package p.ithorns.tools.excel.read;

import cn.idev.excel.analysis.v07.XlsxSaxAnalyser;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.context.AnalysisContext;
import cn.idev.excel.event.AnalysisEventListener;
import cn.idev.excel.exception.ExcelAnalysisException;
import cn.idev.excel.exception.ExcelDataConvertException;
import cn.idev.excel.metadata.CellExtra;
import cn.idev.excel.read.metadata.holder.ReadRowHolder;
import cn.idev.excel.read.metadata.holder.ReadSheetHolder;
import cn.idev.excel.util.SheetUtils;
import cn.idev.excel.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.hibernate.validator.HibernateValidator;
import p.ithorns.tools.excel.AssistTools;
import p.ithorns.tools.excel.EasyExcelUtil;
import p.ithorns.tools.excel.exception.FieldValidateException;
import p.ithorns.tools.excel.exception.ImportException;
import p.ithorns.tools.excel.exception.RowValidateException;
import p.ithorns.tools.excel.model.FieldInfo;
import p.ithorns.tools.excel.model.RowError;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import java.lang.reflect.ParameterizedType;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.LongAdder;

/**
 * excel导入处理基类
 *
 * @author excel导入处理基类
 * @since 2023/5/31 18:44
 */
@SuppressWarnings({"unchecked", "rawtypes"})
@Slf4j
public abstract class BaseImportHandler<T> extends AnalysisEventListener<T> {

    /**
     * 批次数量
     */
    private static final int BATCH_SIZE = 1000;


    /**
     * 导入类型
     */
    private final Class<T> importClass;


    /**
     * 字段信息
     */
    private final Map<String, FieldInfo> filedInfoMap;


    /**
     * 临时数据存储
     */
    private Map<Integer, T> importMap = new ConcurrentHashMap<>(BATCH_SIZE);

    /**
     * 错误行信息
     */
    private List<RowError<T>> rowErrors;

    /**
     * 处理成功数量
     */
    private final LongAdder successNum;

    /**
     * 参数缓存
     */
    private Map<String, Object> cacheParamMap;

    /**
     * 字段校验器
     */
    private final static Validator VALIDATOR;

    static {
        try (ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure()
                // 快速失败模式
                .failFast(true).buildValidatorFactory()) {
            VALIDATOR = validatorFactory.getValidator();
        }
    }

    private final Object lock = new Object();

    public BaseImportHandler() {
        // 获取泛型
        this.importClass = (Class<T>) ((ParameterizedType) this.getClass()
                .getGenericSuperclass())
                .getActualTypeArguments()[0];

        // 字段表
        this.filedInfoMap = AssistTools.resolveExcelFieldMap(importClass);

        this.successNum = new LongAdder();
    }


    /**
     * Sheet工作表名称
     * <p>
     * 注意：如果这里设置了SheetName，就必须和下载模版方法({@link EasyExcelUtil#doExport})设置的值保持一致！<br/>
     * {@code EasyExcel.sheet(sheetName)}
     * <p>
     * 参考{@link XlsxSaxAnalyser#execute}方法中line#257{@link SheetUtils#match}
     *
     * @return sheet页名称
     */
    public abstract String getSheetName();


    /**
     * 入库
     *
     * @param imports -待导入的批数据列表(已过滤)
     */
    public abstract void saveToDb(final List<T> imports);


    /**
     * 在读取表格前做准备工作
     * <p>
     * 这个操作是全局的，只会操作一次，在表头检验通过后执行，
     */
    public void doBeforeAllAnalysed() {
    }


    /**
     * 表格标题行号
     * <p>
     * 默认为1
     *
     * @return 表格标题行数
     */
    public int getHeadNumber() {
        return 2;
    }


    /**
     * 入库前操作
     *
     * @param imports -待导入的批数据列表
     */
    public void doBeforeSave(List<T> imports) {
    }

    /**
     * 校验行记录
     * <p>
     * <p>
     * 在{@link #doBeforeSave}后执行
     * 经过行校验后，会对临时导入列表进行过滤
     *
     * @param row -行记录
     */
    public void validateRow(T row) throws RowValidateException {
    }

    /**
     * 导入后操作
     *
     * @param imports -导入的批数据列表
     */
    public void doAfterSave(final List<T> imports) {
    }


    /**
     * 每批处理数量
     *
     * @return 数量值, 默认为BATCH_COUNT(1000)
     */
    protected int getBatchSize() {
        return BATCH_SIZE;
    }

    /**
     * 设置缓存参数
     * <p>
     * 给后面流程使用
     *
     * @return 表格标题行数
     */
    public Map<String, Object> buildCacheParamMapForNext() {
        return Optional.ofNullable(cacheParamMap).orElse(new HashMap<>(1));
    }


    /**
     * 导入类
     *
     * @return Class<T>具体导入类
     */
    public Class<T> getImportClass() {
        return importClass;
    }

    /**
     * 错误行信息
     *
     * @return 错误列表
     */
    public List<RowError<T>> getRowErrors() {
        return rowErrors;
    }


    /**
     * 导入成功数量
     *
     * @return -成功数量
     */
    public int getSuccessNum() {
        return successNum.intValue();
    }

    /**
     * 获取缓存参数 给后面流程使用
     *
     * @param allCacheData 之前流程的缓存数据
     */
    public void initCacheParamMap(Map<String, Object> allCacheData) {
        cacheParamMap = allCacheData;
    }

    /**
     * 缓存参数
     * <p>
     * 默认为cacheParamMap
     *
     * @return Map<String, Object>
     */
    public Map<String, Object> getCacheParamMap() {
        return cacheParamMap;
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        // 重置数据
        this.reset();

        final int rowIndex = context.readRowHolder().getRowIndex();
        Collection<FieldInfo> fieldInfos = filedInfoMap.values();
        for (FieldInfo fi : fieldInfos) {
            // 跳过非表头行
            int headRow = fi.getHeadRow();
            if (rowIndex != headRow) {
                return;
            }

            int columnIndex = fi.getIndex();
            String headName = headMap.get(columnIndex);
            if (StringUtils.isEmpty(headName)) {
                throw new ExcelAnalysisException("Excel数据解析异常: 请检查Excel表格式是否正确.");
            }

            String fieldAlias = fi.getMainAlias();
            if (!headName.equals(fieldAlias)) {
                throw new ExcelAnalysisException("Excel数据解析异常，请检查Excel表列头是否对应.");
            }
        }

        // 只做一次初始化
        if (rowIndex == 0) {
            // 表头检验通过后执行数据校验前操作
            doBeforeAllAnalysed();
        }
    }


    /**
     * 这里的调用路径是
     * -> cn.idev.excel.analysis.ExcelReadExecutor#execute()
     * -> com.sun.org.apache.xerces.internal.parsers.XMLParser#parse(com.sun.org.apache.xerces.internal.xni.parser.XMLInputSource)
     * -> cn.idev.excel.read.processor.AnalysisEventProcessor#endRow(cn.idev.excel.context.AnalysisContext)
     * -> cn.idev.excel.read.processor.DefaultAnalysisEventProcessor#dealData(cn.idev.excel.context.AnalysisContext)
     *
     * @param t       one row value. It is same as {@link AnalysisContext#readRowHolder()}
     * @param context analysis context
     */
    @Override
    public void invoke(T t, AnalysisContext context) {
        // 校验行数据
        validateImportItem(t, context);

        // 加入到待导入列表
        Integer rowIndex = context.readRowHolder().getRowIndex();
        importMap.put(rowIndex, t);

        // 批处理100
        if (importMap.size() >= getBatchSize()) {
            // 进行批处理
            doImportBatch(context);
        }
    }


    /**
     * 校验行记录
     *
     * @param record  -行记录
     * @param context -Excel上下文
     */
    private void validateImportItem(T record, AnalysisContext context) {
        // 表数据
        ReadSheetHolder readSheetHolder = context.readSheetHolder();
        // ReadCellData<?> tempCellData = readSheetHolder.getTempCellData();
        // String sheetName = readSheetHolder.getSheetName();

        // 行索引
        final Integer rowIndex = readSheetHolder.getRowIndex();

        Set<ConstraintViolation<T>> constraintSet = VALIDATOR.validate(record);
        if (CollectionUtils.isNotEmpty(constraintSet)) {
            constraintSet.forEach(cv -> {
                // 字段名
                String filedName = cv.getPropertyPath().toString();
                FieldInfo fieldInfo = filedInfoMap.get(filedName);

                Integer fieldIndex = null;
                String fieldAlias = null;

                if (null != fieldInfo) {
                    // 列索引
                    fieldIndex = fieldInfo.getIndex() + 1;
                    // 列别名
                    fieldAlias = fieldInfo.getMainAlias();
                }

                String msg = MessageFormat.format("{0}->{1}", fieldAlias, cv.getMessage());
                throw new FieldValidateException(rowIndex, fieldIndex, fieldAlias, msg);
            });
        }
    }


    /**
     * 这里的调用路径是
     * -> cn.idev.excel.read.processor.AnalysisEventProcessor#endSheet(cn.idev.excel.context.AnalysisContext)
     * 所以这里出异常，不会被上层捕获并调用 {@link #onException}方法，需要自己处理
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        log.info("所有数据解析完成!");
        // 进行批处理  // 解析所有数据后，context中的ReadRowHolder也就到最后一个了
        doImportBatch(context);
    }

    /**
     * 执行导入
     */
    private void doImportBatch(AnalysisContext context) {
        if (org.springframework.util.CollectionUtils.isEmpty(importMap)) {
            return;
        }

        long a = System.currentTimeMillis();
        log.info("开始单次批处理");

        Collection<T> values = importMap.values();
        List<T> importList = new ArrayList<>(values);
        // 自定义入库前校验
        doBeforeSave(importList);

        // 入库前校验
        validateBeforeSave(context, importMap);

        List<T> finalList = new ArrayList<>(importMap.values());
        // 入库
        saveToDb(finalList);

        // 自定义入库后操作
        doAfterSave(finalList);

        // 累加成功数量
        successNum.add(finalList.size());

        // 存储完成清理 list
        this.importMap = new ConcurrentHashMap<>(BATCH_SIZE);
        log.info("结束单次批处理, 耗时:{}", System.currentTimeMillis() - a);
    }

    /**
     * 入库前校验
     *
     * @param imports -待导入的Excel数据
     */
    private void validateBeforeSave(AnalysisContext context,
                                    final Map<Integer, T> imports) {
        ReadSheetHolder sheet = context.readSheetHolder();
        Set<Map.Entry<Integer, T>> entries = imports.entrySet();
        Iterator<Map.Entry<Integer, T>> iter = entries.iterator();
        synchronized (lock) {
            while (iter.hasNext()) {
                Map.Entry<Integer, T> entry = iter.next();
                Integer rowIndex = entry.getKey();
                T xls = entry.getValue();
                try {
                    // 用户自定义行校验
                    validateRow(xls);
                } catch (RowValidateException e) {
                    iter.remove();
                    onException(sheet, rowIndex, xls, e);
                }
            }
        }
    }

    /**
     * 本地校验错误 -
     *
     * @param sheet
     * @param xls
     */
    private void onException(ReadSheetHolder sheet, Integer rowIndex, T xls, RowValidateException e) {
        Integer sheetNo = sheet.getSheetNo();
        String sheetName = sheet.getSheetName();
        String message = e.getMessage();
        log.error("Excel字段校验异常(表{}第{}行): {}.", sheetNo, rowIndex, message);
        RowError<T> rowError = RowError.<T>builder()
                .sheetName(sheetName)
                .sheetNo(sheetNo)
                .rowIndex(rowIndex)
                .msg(message)
                .rowData(xls)
                .build();

        rowErrors.add(rowError);
    }

    @Override
    public void onException(Exception e, AnalysisContext context) {
        // 数据转换异常
        if (e instanceof ExcelDataConvertException) {
            RowError<T> error = handleDataConvertError(context, (ExcelDataConvertException) e);
            rowErrors.add(error);
            return;
        }

        // 字段校验异常
        if (e instanceof FieldValidateException) {
            RowError<T> error = handleFieldValidateError(context, (FieldValidateException) e);
            rowErrors.add(error);
            return;
        }

        // 行数据校验异常
        if (e instanceof RowValidateException) {
            RowError<T> error = handleRowValidateError(context, (RowValidateException) e);
            rowErrors.add(error);
            return;
        }

        String message = e.getMessage();

        // 表
        ReadSheetHolder sheet = context.readSheetHolder();
        Integer sheetNo = sheet.getSheetNo();

        // 如果是某一个单元格的转换异常 能获取到具体行号
        ReadRowHolder row = context.readRowHolder();
        Integer rowIndex = row.getRowIndex();

        log.error("Excel数据解析异常(表{}第{}行): {}.", sheetNo, rowIndex, message);

        // 其他异常直接抛出，终止解析。 例如: 表头不对应
        throw new ImportException(message);
    }

    /**
     * 重置/清理缓存数据
     */
    public void reset() {
        this.importMap = new ConcurrentHashMap<>(BATCH_SIZE);
        this.rowErrors = new ArrayList<>(getBatchSize());
        this.successNum.reset();
    }

    private RowError<T> handleDataConvertError(AnalysisContext context, ExcelDataConvertException e) {
        ReadSheetHolder sheet = context.readSheetHolder();
        ReadRowHolder row = context.readRowHolder();
        Integer rowIndex = e.getRowIndex();
        Integer columnIndex = e.getColumnIndex();

        String message = e.getMessage();
        Integer sheetNo = sheet.getSheetNo();
        String sheetName = sheet.getSheetName();

        Object rowData = AssistTools.convertRowToXlsBean((Map) row.getCellMap(), sheet, context);

        RowError.RowErrorBuilder<T> builder = RowError.<T>builder()
                .sheetName(sheetName)
                .sheetNo(sheetNo)
                .rowIndex(rowIndex)
                .msg(message)
                .rowData((T) rowData);

        log.error("Excel数据转换异常(表{}第{}行第{}列): {}", sheetNo, rowIndex, columnIndex, message);
        String[] names = e.getExcelContentProperty().getField().getAnnotation(ExcelProperty.class).value();
        if (null != names && names.length > 0) {
            builder.columnIndex(columnIndex).columnName(names[0]);
        }

        return builder.build();
    }

    private RowError<T> handleFieldValidateError(AnalysisContext context, FieldValidateException e) {
        ReadSheetHolder sheet = context.readSheetHolder();
        ReadRowHolder row = context.readRowHolder();

        Integer rowIndex = e.getRowIndex();
        Integer columnIndex = e.getColumnIndex();
        String columnName = e.getColumnName();

        String message = e.getMessage();
        Integer sheetNo = sheet.getSheetNo();
        String sheetName = sheet.getSheetName();

        Object rowData = AssistTools.convertRowToXlsBean((Map) row.getCellMap(), sheet, context);
        log.error("Excel字段校验异常(表{}第{}行第{}列): {}.", sheetNo, rowIndex, columnIndex, message);
        return RowError.<T>builder()
                .sheetName(sheetName)
                .sheetNo(sheetNo)
                .rowIndex(rowIndex)
                .columnIndex(columnIndex)
                .columnName(columnName)
                .msg(message)
                .rowData((T) rowData)
                .build();
    }

    private RowError<T> handleRowValidateError(AnalysisContext context, RowValidateException e) {
        ReadSheetHolder sheet = context.readSheetHolder();
        ReadRowHolder row = context.readRowHolder();

        Integer rowIndex = sheet.getRowIndex();
        String message = e.getMessage();
        Integer sheetNo = sheet.getSheetNo();
        String sheetName = sheet.getSheetName();

        Object rowData = AssistTools.convertRowToXlsBean((Map) row.getCellMap(), sheet, context);
        log.error("Excel行校验异常(表{}第{}行): {}.", sheetNo, rowIndex, message);
        return RowError.<T>builder()
                .sheetName(sheetName)
                .sheetNo(sheetNo)
                .rowIndex(rowIndex)
                .msg(message)
                .rowData((T) rowData)
                .build();
    }

}